#include "pari-python.h"
#include "gen.h"
#include "args.h"
#include "errors.h"
//error in quadunit should be G not Gp
//error in subgrouplist should be GDGD0,L, not GDGD0,L,p
// the following functions are not present: geni, alias0, allocatemem0, break0, 
// next0, return0, trap0, gpwritebin
// I,alias,allocatemem,break,next,return,trap,writebin
/* ----------------------------------------------------- */
PyTypeObject* type_ptr;
PyObject *pari_error;
PyObject* pari_module;
long pari_real_prec = 5;
long pari_series_prec = 16;

static PyObject *
pari_eval(PyObject *self, PyObject *args) {
	char* s;
	if(!PyArg_ParseTuple(args, "s", &s)) return NULL;
	//printf("parsed %s\n", s);
	GEN g = gp_read_str(s);
	//printf("GP executed %X\n", g);
	char* res = GENtostr(g);
	printf(" *** result: %s\n", res);
	free(res);
	Py_INCREF(Py_None);
	return Py_None;
}

static PyObject *
pari_parse(PyObject *self, PyObject *args) {
	char* s;
	if(!PyArg_ParseTuple(args, "s", &s)) return NULL;
	//printf("parsed %s\n", s);
	CATCH_PARI {
		SET_ERROR;
		return NULL;
	} TRY_PARI {
		GEN g = gp_read_str(s);
		readseq_void(s);
		PyObject *ret = (PyObject*) create_gen(g);
		return ret;
	} ENDCATCH_PARI
}

static PyObject *
pari_cast(PyObject *self, PyObject *args) {
	PyObject* obj;
	if(!PyArg_ParseTuple(args, "O", &obj)) return NULL;
	//printf("parsed %s\n", s);
	CATCH_PARI {
		SET_ERROR;
		return NULL;
	} TRY_PARI {
		return (PyObject*) create_gen(gen_cast(obj));
	} ENDCATCH_PARI
}

GEN get_var(char* name) {
	long v = fetch_user_var(name);
	GEN g = pol_x[v];
	return g;
}

#define O_ERROR pari_err(talker, "Argument to O must be of type x^n where x is a prime or a variable")
static PyObject *
pari_O(PyObject* self, PyObject* args) {
	PyObject* _obj;
	if(!PyArg_ParseTuple(args, "O", &_obj)) return NULL;
	pari_sp __av = avma;
	GEN g = gen_cast(_obj);
	if(g==NULL) {
		avma = __av;
		return NULL;
	}
	CATCH_PARI {
		SET_ERROR;
		avma = __av;
		return NULL;
	} TRY_PARI {
		GEN fact = factor(g);
		if(typ(fact)!=t_MAT || lg(fact)!=3 || typ(fact[1])!=t_COL || lg(fact[1])!=2) {
			O_ERROR;
		}
		GEN x = (GEN)((GEN (fact[1]))[1]);
		GEN n = (GEN)((GEN (fact[2]))[1]);
		if((typ(x)!=t_INT && typ(x)!=t_POL) || typ(n)!=t_INT) {
			O_ERROR;
		}
		GEN res = NULL;
		if(typ(x)==t_INT) {
			res = zeropadic(x,itos(n));
		} else {// t_POL
			if(degpol(x)!=1 || !gcmp0(constant_term(x)) || !gcmp1(leading_term(x))) {
				O_ERROR;
			}
			res = zeroser(gvar(x), itos(n));
		}
		return (PyObject*) create_gen(res);
	} ENDCATCH_PARI		
}

static PyObject *
pari_var(PyObject *self, PyObject *args) {
	char* s;
	if(!PyArg_ParseTuple(args, "s", &s)) return NULL;
	//printf("parsed %s\n", s);
	CATCH_PARI {
		SET_ERROR;
		return NULL;
	} TRY_PARI {
		PyObject *ret = (PyObject*) create_gen(get_var(s));
		return ret;
	} ENDCATCH_PARI
}

static PyObject* 
gs_real_prec(PyObject*, PyObject* args) { 
	if(PyTuple_Size(args)==0) { 
		return PyInt_FromLong(pari_real_prec); 
	} else { 
		if(!PyArg_ParseTuple(args, "l", &pari_real_prec)) return NULL; 
		precreal = pari_real_prec;
		GP_DATA->fmt->sigd = pari_real_prec;
		Py_INCREF(Py_None); 
		return Py_None; 
	} 
}

static PyObject* 
gs_series_prec(PyObject*, PyObject* args) { 
	if(PyTuple_Size(args)==0) { 
		return PyInt_FromLong(pari_series_prec); 
	} else { 
		if(!PyArg_ParseTuple(args, "l", &pari_series_prec)) return NULL; 
		precdl = pari_series_prec;
		Py_INCREF(Py_None); 
		return Py_None; 
	}
}


static struct PyMethodDef pari_methods[] = {
	{"gp", pari_eval, METH_VARARGS, "Evaluates expression in pari and prints the result"},
	{"parse", pari_parse, METH_VARARGS, "Evaluates string expression in pari and returns an object"},
	{"cast", pari_cast, METH_VARARGS, "Transforms a Python object, e.g. a number, to a pari object"},
	{"var", pari_var, METH_VARARGS, "Creates a variable in PARI and returns the corresponding object"},
	{"O", pari_O, METH_VARARGS, "Creates a power series or a p-adic number O(x^n)"},
	{"real_prec", gs_real_prec, METH_VARARGS, "Gets and sets the default real precision"},
	{"ser_prec", gs_series_prec, METH_VARARGS, "Gets and sets the default series precision"},
	{"div_behavior", gs_div_behavior, METH_VARARGS, "Gets and sets the behavior of '/' operator. 1 - use rationals, 0 - standard"},
	{NULL, NULL}
};

PyMODINIT_FUNC
init_pari()
{
	pari_GEN.ob_type = &PyType_Type;
	type_ptr = &pari_GEN;
	// init PARI
	pari_init_opts(512000000, 500000, INIT_DFTm);
    // init PARI exception handler and expression handler
	default_exception_handler = exception_handler;
	foreignExprHandler = callback_func;
	// init module
	pari_module = Py_InitModule("pari._pari", pari_methods);
	PyObject* core_module = Py_InitModule("pari._pari.core", core_methods);
	// create exception type
	pari_error = PyErr_NewException("pari.error", NULL, NULL);
	// default precision
	pari_real_prec = 28;
	pari_series_prec = 16;
	GP_DATA->fmt->sigd = pari_real_prec;
	// variables
	Py_INCREF(pari_error); // need it because module steals reference
	PyModule_AddObject(pari_module, "error", pari_error);
	Py_INCREF(core_module); // need it because module steals reference
	PyModule_AddObject(pari_module, "core", core_module);
	Py_INCREF(type_ptr); // need it because module steals reference
	PyModule_AddObject(pari_module, "GEN", (PyObject*) type_ptr);
}
